using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Xml;

namespace Woz
{
    /// <summary>
    /// base class for entity that implements both idisposable and icloneable
    /// </summary>
    public abstract class Entity : IDisposable, ICloneable
    {
        /// <summary>
        /// entity constructor
        /// </summary>
        public Entity(Point pPosition, Size pSize)
        {
            // increment the autoid
            sAutoID++;

            // set the id
            this.mID = sAutoID;

            // set the position
            this.mBoundingBox.X = pPosition.X;
            this.mBoundingBox.Y = pPosition.Y;

            // set the size
            this.mBoundingBox.Width = pSize.Width;
            this.mBoundingBox.Height = pSize.Height;
        }

        /// <summary>
        /// get the entity ID
        /// </summary>
        public uint ID
        {
            get
            {
                return this.mID;
            }
            //protected set
            //{
            //    // set the id
            //    this.mID = value;

            //    // update the auto id
            //    sAutoID = Math.Max(sAutoID, value + 1);
            //}
        }

        /// <summary>
        /// get or set the position of the entity within the screen
        /// </summary>
        public Point Position
        {
            get
            {
                return this.mBoundingBox.Location;
            }

            set
            {
                // set the position
                this.OnMove(value.X - this.mBoundingBox.X, value.Y - this.mBoundingBox.Y);
            }
        }

        /// <summary>
        /// the size of the entity
        /// </summary>
        public Size Size
        {
            get
            {
                return this.mBoundingBox.Size;
            }
            
            set
            {
                // set the size
                this.OnScale(value.Width - this.mBoundingBox.Width, value.Height - this.mBoundingBox.Height);
            }
        }

        /// <summary>
        /// readonly gets the bounding box of this entity in pixels
        /// </summary>
        public Rectangle BoundingBox
        {
            get
            {
                return this.mBoundingBox;
            }
        }

        /// <summary>
        /// get or set the number of screens refering to this entity
        /// </summary>
        public int RefCount
        {
            get
            {
                return this.mRefCount;
            }
            set
            {
                this.mRefCount = value;
            }
        }

        /// <summary>
        /// get or set whether the entity is locked or not
        /// </summary>
        public bool Locked
        {
            get
            {
                return this.mLocked;
            }
            set
            {
                this.mLocked = value;
            }
        }

        /// <summary>
        /// computes the bounding box for a bunch of entities
        /// </summary>
        /// <param name="pEntities">the entities to compute a bounding box for</param>
        /// <returns>a bounding box around all of the entities in the list</returns>
        public static Rectangle ComputeBoundingBox(Woz.Entity[] pEntities)
        {
            // if the list of entities is empty
            if (pEntities.Length == 0)
            {
                // then use the empty rectangle
                return Rectangle.Empty;
            }

            // use the first item as the base bounding box
            Rectangle boundingBox = pEntities[0].BoundingBox;

            // now keep adding the bounding boxes for all the entities
            for (int i = 1; i < pEntities.Length; i++)
            {
                boundingBox = Rectangle.Union(boundingBox, pEntities[i].BoundingBox);
            }

            // the total bounding box is the union of all the bounding boxes
            return boundingBox;
        }

        /// <summary>
        /// figure out whether or not entity lies within
        /// given pair of points 
        /// </summary>
        /// <param name="pX">x position to check</param>
        /// <param name="pY">y position to check</param>
        /// <returns>true if entity lies within pX, pY</returns>
        public abstract bool HitTest(int pX, int pY);

        /// <summary>
        /// tells entity to draw itself using the graphics object
        /// </summary>
        /// <param name="pGraphics">the graphics object to use</param>
        public abstract void Draw(Graphics pGraphics);

        /// <summary>
        /// move entity to new location
        /// </summary>
        /// <param name="pLocation">the new location to move entity to</param>
        public void Move(Point pLocation)
        {
            // move the entity
            this.OnMove(pLocation.X - this.mBoundingBox.X, pLocation.Y - this.mBoundingBox.Y);
        }

        /// <summary>
        /// move entity to x and y coordinates
        /// </summary>
        /// <param name="pX">the absolute x position</param>
        /// <param name="pY">the absolute y position</param>
        public void Move(int pX, int pY)
        {
            // move the entity
            this.OnMove(pX - this.mBoundingBox.X, pY - this.mBoundingBox.Y);
        }

        /// <summary>
        /// translate the entity by x and y delta values
        /// </summary>
        /// <param name="pDeltaX">the relative x amount to move</param>
        /// <param name="pDeltaY">the relative y amount to move</param>
        public void Translate(int pDeltaX, int pDeltaY)
        {
            // move the entity
            this.OnMove(pDeltaX, pDeltaY);
        }

        /// <summary>
        /// scale the entity by the relative width and height
        /// </summary>
        /// <param name="pDeltaWidth">the relative width to scale by</param>
        /// <param name="pDeltaHeight">the relative height to scale by</param>
        public void Scale(int pDeltaWidth, int pDeltaHeight)
        {
            // scale the entity
            this.OnScale(pDeltaWidth, pDeltaHeight);
        }

        /// <summary>
        /// saves the entity to the given stream
        /// </summary>
        /// <param name="pStream">the stream to save the entity to</param>
        /// <param name="pPath">the path of the temporary files</param>
        public void Save(XmlWriter pStream, string pPath)
        {
            // now write yourself
            pStream.WriteStartElement("Entity", "urn:Woz");
            pStream.WriteAttributeString("ID", this.mID.ToString());
            pStream.WriteElementString("Top", this.mBoundingBox.Top.ToString());
            pStream.WriteElementString("Left", this.mBoundingBox.Left.ToString());
            pStream.WriteElementString("Width", this.mBoundingBox.Width.ToString());
            pStream.WriteElementString("Height", this.mBoundingBox.Height.ToString());
            pStream.WriteElementString("Locked", this.mLocked.ToString());
            pStream.WriteElementString("Type", this.GetType().FullName);

            // save the actual entity
            this.OnSave(pStream, pPath);

            // close entity
            pStream.WriteEndElement();
        }

        /// <summary>
        /// dispose this entity
        /// </summary>
        public abstract void Dispose();

        /// <summary>
        /// create an exact copy of this entity and return it
        /// </summary>
        /// <returns>an exact duplicate of this entity</returns>
        public abstract object Clone();

        /// <summary>
        /// checks whether or not this entity supports an attribute
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <returns>true if attribute is supported</returns>
        public abstract bool SupportsAttribute(string pAttribute);

        /// <summary>
        /// gets a reference to the attribute with the given attribute name
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <returns>a reference to the attribute</returns>
        public abstract object GetAttribute(string pAttribute);

        /// <summary>
        /// saves a reference to the attribute with the given attribute name
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <param name="pObject">a reference to the attribute</param>
        public abstract void SetAttribute(string pAttribute, object pObject);

        /// <summary>
        /// the actual moving of the entity happens here
        /// </summary>
        /// <param name="pDeltaX">the relative x amount to move entity by</param>
        /// <param name="pDeltaY">the relative y amount to move entity by</param>
        protected abstract void OnMove(int pDeltaX, int pDeltaY);

        /// <summary>
        /// the actual scaling of the entity happens here
        /// </summary>
        /// <param name="pDeltaWidth">the relative amount of width</param>
        /// <param name="pDeltaHeight">the relative amount of height</param>
        protected abstract void OnScale(int pDeltaWidth, int pDeltaHeight);

        /// <summary>
        /// the actual saving of the entity happens here
        /// </summary>
        /// <param name="pStream">the stream to save to</param>
        /// <param name="pPath">the path of the temporary files</param>
        protected abstract void OnSave(XmlWriter pStream, string pPath);

        /// <summary>
        /// the bouding box of the entity which contains a size and position
        /// </summary>
        protected Rectangle mBoundingBox;

        /// <summary>
        /// number of screen refering this entity
        /// </summary>
        private int mRefCount;

        /// <summary>
        /// whether or not this entity is locked
        /// </summary>
        private bool mLocked;

        /// <summary>
        /// the id of the entity
        /// </summary>
        private uint mID;

        /// <summary>
        /// autoid for entities
        /// </summary>
        private static uint sAutoID;
    }
}
